/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) JcaCipherApi.java 2018-08-06 17:26
 */

package cn.jh.common.core.crypto;

import cn.jh.common.core.base.HexKt;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;


/**
 * 基于JCA（Java Connector Architecture）的密钥算法API的基础实现。
 *
 * @author Fuchun
 * @since 1.0
 */
public abstract class JcaCipherApi implements CipherApi {

    protected static final byte[] EMPTY_BYTES = new byte[0];

    static {
        try {
            Class.forName("com.huishu.common.crypto.CryptoInit");
        } catch (ClassNotFoundException ex) {
            throw new InternalError(ex);
        }
    }

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    protected final CipherParams params;
    SecureRandom secureRandom;

    protected JcaCipherApi(CipherParams params) {
        this.params = params;
        initialize();
    }

    protected void initialize() {
    }

    @Override
    public String getAlgorithm() {
        return params.getAlgorithm();
    }

    @Override
    public String getProvider() {
        return params.getProvider();
    }

    public CipherParams getParams() {
        return params;
    }

    public SecureRandom getSecureRandom() {
        return secureRandom;
    }

    public void setSecureRandom(SecureRandom secureRandom) {
        this.secureRandom = secureRandom;
    }

    /**
     * 根据给定的数据生成指定算法下的加密参数的规范。
     *
     * @return 加密参数的（透明）规范。
     */
    protected abstract AlgorithmParameterSpec toParameterSpec(byte[] parameters);

    @Override
    public byte[] encrypt(byte[] plaintext, byte[] key) throws CryptoException {
        return encrypt(plaintext, key, null);
    }

    @Override
    public byte[] encrypt(byte[] plaintext, Key key) throws CryptoException {
        return encrypt(plaintext, key, null);
    }

    @Override
    public byte[] decrypt(byte[] cipherText, byte[] key) throws CryptoException {
        return decrypt(cipherText, key, null);
    }

    @Override
    public byte[] decrypt(byte[] cipherText, Key key) throws CryptoException {
        return decrypt(cipherText, key, null);
    }

    protected byte[] encrypt(byte[] plaintext, byte[] key, @Nullable byte[] iv) {
        return cryptWithKeyBytes(plaintext, key, iv, Cipher.ENCRYPT_MODE);
    }

    protected byte[] encrypt(byte[] plaintext, Key key, @Nullable byte[] iv) {
        return cryptWithKey(plaintext, key, iv, Cipher.ENCRYPT_MODE);
    }

    protected byte[] decrypt(byte[] cipherText, byte[] key, @Nullable byte[] parameters) {
        return cryptWithKeyBytes(cipherText, key, parameters, Cipher.DECRYPT_MODE);
    }

    protected byte[] decrypt(byte[] cipherText, Key key, @Nullable byte[] parameters) {
        return cryptWithKey(cipherText, key, parameters, Cipher.DECRYPT_MODE);
    }

    protected byte[] cryptWithKeyBytes(
            byte[] bytes, byte[] key, @Nullable byte[] parameters,
            @MagicConstant(intValues = { Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE }) int mode) throws CryptoException {
        if (key == null || key.length == 0) throw new IllegalArgumentException("key argument cannot be empty.");
        Cipher cipher = initNewCipher(mode, key, parameters);
        return cryptWithKey(cipher, bytes);
    }

    protected byte[] cryptWithKey(
            byte[] bytes, Key key, @Nullable byte[] parameters,
            @MagicConstant(intValues = { Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE }) int mode) throws CryptoException {
        Cipher cipher = initNewCipher(mode, key, parameters);
        return cryptWithKey(cipher, bytes);
    }

    protected byte[] cryptWithKey(Cipher cipher, byte[] bytes) throws CryptoException {
        try {
            return cipher.doFinal(bytes);
        } catch (Exception ex) {
            throw new CryptoException("Unable to execute 'doFinal' with cipher instance [" + cipher + "].", ex);
        }
    }

    protected Cipher initNewCipher(
            @MagicConstant(intValues = { Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE }) int mode,
            byte[] key, byte[] parameters) {
        SecretKeySpec jdkKey = new SecretKeySpec(key, getAlgorithm());
        return initNewCipher(mode, jdkKey, parameters);
    }

    protected Cipher initNewCipher(
            @MagicConstant(intValues = { Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE }) int mode,
            Key key, byte[] parameters) {
        Cipher cipher = newCipherInstance();
        AlgorithmParameterSpec paramSpec = null;
        if (parameters != null) {
            paramSpec = toParameterSpec(parameters);
        }
        initCipher(cipher, mode, key, paramSpec, getSecureRandom());
        return cipher;
    }

    /**
     * 初始化指定的 {@code Cipher}。
     *
     * @param cipher 要初始化的 {@code Cipher}
     * @param mode   更新模式 (Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE)
     * @param key    密钥。
     * @param spec   算法的相关参数。
     * @param random 安全随机数。
     * @throws CryptoException 如果密钥不合法，或者算法参数不合法。
     */
    protected void initCipher(
            Cipher cipher, @MagicConstant(intValues = { Cipher.ENCRYPT_MODE, Cipher.DECRYPT_MODE }) int mode,
            Key key, AlgorithmParameterSpec spec,
            SecureRandom random) {
        try {
            if (random != null) {
                if (spec != null) {
                    cipher.init(mode, key, spec, random);
                } else {
                    cipher.init(mode, key, random);
                }
            } else {
                if (spec != null) {
                    cipher.init(mode, key, spec);
                } else {
                    cipher.init(mode, key);
                }
            }
        } catch (InvalidKeyException ex) {
            throw new CryptoException("The key hex[" + HexKt.encode(key.getEncoded()) + "] is invalid " +
                    "for '" + getAlgorithm() + "' transformation.", ex);
        } catch (InvalidAlgorithmParameterException ex) {
            throw new CryptoException("The algorithm '" + getAlgorithm() + "' is invalid or inappropriate parameter.");
        }
    }

    /**
     * 获取一个新的 `Cipher` 实例。
     * 获取实例时的 `transformation` 和 `provider` 由 `setting` 指定。
     *
     * @return 实现所请求转换的 `Cipher`。
     * @throws CryptoException the {@code transformation.padding}, {@code transformation.algorithm}
     * or {@code params.provider} is invalid.
     */
    protected Cipher newCipherInstance() throws CryptoException {
        String transformationString = params.getTransformation().toString();
        try {
            return Cipher.getInstance(transformationString, getProvider());
        } catch (Exception ex) {
            // 一般情况下，只要指定的 provider 存在，则不会发生此异常
            // 例如：指定了 "BC" 作为密钥算法提供者，但是运行库里没有 bouncy-castle，则会抛出此异常
            String msg = "Unable to acquire a Java JCA Cipher instance using " +
                    "Cipher.getInstance(\"" + transformationString + "\", \"" +
                    getProvider() + "\"). " + getAlgorithm() +
                    " under this configuration is required for the " +
                    getClass().getName() + " instance to function.";
            throw new CryptoException(msg, ex);
        }
    }
}
